package com.gitee.huanminabc.multimedia.ffmpeg;

import java.io.File;
import java.util.ArrayList;
import java.util.List;

public class FfmpegAudioUtil {

    //音频转码
    //音频转码,支持格式有:mp3,flac,wav,aac,ogg,amr,pcm,alaw,ulaw,opus
    //如果是音频转码,那么输出路径的后缀名就是音频的格式
    public static void transferFormat(String inputPath, String outputPath) {
        List<String> cmds = new ArrayList<>();
        cmds.add("-i");
        cmds.add(inputPath);
        cmds.add("-y");
        cmds.add(outputPath);
        FfmpegInfoUtil.ExecuteCmd(cmds);
    }

    //音频裁剪
    //音频裁剪,单位是秒
    public static void cutAudio(String inputPath, String outputPath, int start, int end) {
        List<String> cmds = new ArrayList<>();
        cmds.add("-i");
        cmds.add(inputPath);
        cmds.add("-y");
        cmds.add("-ss");
        cmds.add(String.valueOf(start));
        cmds.add("-to");
        cmds.add(String.valueOf(end));
        cmds.add(outputPath);
        FfmpegInfoUtil.ExecuteCmd(cmds);
    }
    //音频合并
    //音频合并,支持多个音频合并
    public static void mergeAudio(String outputPath, String... inputPaths) {
        List<String> cmds = new ArrayList<>();
        cmds.add("-i");
        cmds.add(inputPaths[0]);
        for (int i = 1; i < inputPaths.length; i++) {
            cmds.add("-i");
            cmds.add(inputPaths[i]);
        }
        cmds.add("-y");
        cmds.add("-filter_complex");
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < inputPaths.length; i++) {
            sb.append("[").append(i).append(":0]");
        }
        sb.append("concat=n=").append(inputPaths.length).append(":v=0:a=1[outa]");
        cmds.add(sb.toString());
        cmds.add("-map");
        cmds.add("[outa]");
        cmds.add(outputPath);
        FfmpegInfoUtil.ExecuteCmd(cmds);
    }

    //将一个音频插入到另一个音频的指定位置
    public static void insertAudio(String inputPath, String insertPath, String outputPath, int start) {
        List<String> cmds = new ArrayList<>();
        cmds.add("-i");
        cmds.add(inputPath);
        cmds.add("-i");
        cmds.add(insertPath);
        cmds.add("-y");
        cmds.add("-filter_complex");
        cmds.add("[0:a]atrim=end=" + start + "[a0];[1:a]atrim=start=0:end=" + FfmpegInfoUtil.getTime(insertPath) + ",asetpts=PTS-STARTPTS[a1];[0:a]atrim=start=" + start + ",asetpts=PTS-STARTPTS[a2];[a0][a1][a2]concat=n=3:v=0:a=1[outa]");
        cmds.add("-map");
        cmds.add("[outa]");
        cmds.add(outputPath);
        FfmpegInfoUtil.ExecuteCmd(cmds);
    }

    //音频控制音量
    //音频控制音量,支持增大和减小  0就是禁音
    public static void controlVolume(String inputPath, String outputPath, int volume) {
        if (volume < 0) {
            throw new RuntimeException("音量不能小于0");
        }
        List<String> cmds = new ArrayList<>();
        cmds.add("-i");
        cmds.add(inputPath);
        cmds.add("-y");
        cmds.add("-af");
        cmds.add("volume=" + volume + "dB");
        cmds.add(outputPath);
        FfmpegInfoUtil.ExecuteCmd(cmds);
    }
    //音频变速
    //音频变速,支持变速,变速的倍数,1是原速,2是2倍速,0.5是0.5倍速
    public static void changeSpeed(String inputPath, String outputPath, double speed) {
        List<String> cmds = new ArrayList<>();
        cmds.add("-i");
        cmds.add(inputPath);
        cmds.add("-y");
        cmds.add("-filter:a");
        cmds.add("atempo=" + speed);
        cmds.add(outputPath);
        FfmpegInfoUtil.ExecuteCmd(cmds);
    }
    //音频淡入淡出
    //音频淡入淡出,支持淡入淡出,单位是秒
    //意思就是音频开始的时候淡入,从小声音到正常声音,音频结束的时候淡出,从正常声音到小声音
    public static void fadeAudio(String inputPath, String outputPath, int fadein, int fadeout) {
        List<String> cmds = new ArrayList<>();
        cmds.add("-i");
        cmds.add(inputPath);
        cmds.add("-y");
        cmds.add("-af");
        cmds.add("afade=t=in:st=0:d=" + fadein + ",afade=t=out:st=" + (FfmpegInfoUtil.getTime(inputPath) - fadeout) + ":d=" + fadeout);
        cmds.add(outputPath);
        FfmpegInfoUtil.ExecuteCmd(cmds);
    }
    //音频转换为视频
    //音频转换为视频,支持音频转换为视频,视频的背景是黑色也就是啥也没有
    public static void audioToVideo(String inputPath, String outputPath) {
        List<String> cmds = new ArrayList<>();
        cmds.add("-i");
        cmds.add(inputPath);
        cmds.add("-y");
        cmds.add("-f");
        cmds.add("lavfi");
        cmds.add("-i");
        cmds.add("anullsrc=cl=stereo:r=44100");
        cmds.add("-t");
        cmds.add(String.valueOf(FfmpegInfoUtil.getTime(inputPath)));
        cmds.add("-c:v");
        cmds.add("libx264");
        cmds.add("-c:a");
        cmds.add("aac");
        cmds.add(outputPath);
        FfmpegInfoUtil.ExecuteCmd(cmds);
    }

    //音频去噪 , 不能保证百分百去噪,只能说是尽量减少噪音
    //noise是去噪的强度,范围是-80~-20 -20是最强的去噪,-80是最弱的去噪  一般-40~-20就可以了 ,太强会导致声音失真这个需要根据实际情况调整
    //lowpass=3000：这意味着设置一个低通滤波器，允许频率低于或等于3000 Hz的信号通过，而高于这个频率的信号将被衰减。
    //highpass=200：这意味着设置一个高通滤波器，允许频率高于或等于200 Hz的信号通过，而低于这个频率的信号将被衰减。
    //意思声音在 大于200hz和小于3000hz之间的声音会被保留,其他的声音会被去除或者减弱
    //是否合适使用这些设置取决于你的具体需求和音频内容的特性。这些设置可能会适合某些情况，但不适合其他情况。
    // 语音处理：如果你正在处理语音信号，并且希望去除高频噪声（例如背景中的嘶嘶声或某些类型的电子设备噪声），lowpass=3000 可能是合适的，
    // 因为人类语音的大部分能量都在这个频率以下。同时，highpass=200 可以帮助去除低频噪声，如风扇的嗡嗡声或专门的低频干扰。但是，这个高通设置可能会影响到语音信号的低频部分，特别是男声，因为他们的基频（基音）通常较低。
    // 音乐处理：对于音乐，这些设置可能会有不同的影响，取决于音乐的类型和乐器。例如，钢琴的最低音键的频率大约在27.5 Hz，所以这个高通设置会显著影响这些低频音。
    // 同样，一些乐器的泛音（即除基音外的其他音高成分）可能会超过3000 Hz，因此低通滤波器会改变这些声音的音色。
    // 降噪：如果你的目标是降噪，那么这些设置可能不是最佳选择，除非你确切知道噪声所在的频率范围。更专业的降噪方法可能会使用更复杂的滤波器设计，或者基于频谱分析的降噪算法。
    // 一般语音处理，lowpass=3000,highpass=200就可以了
    // 音乐处理,这个就比较复杂了,需要根据音乐的特性来设置,可以适当的调整lowpass和highpass的值来达到最好的效果,如果本身就是高质量的音乐,那么不需要去噪
    // 常见的高频截止可能在15,000 Hz到20,000 Hz之间，低频截止可能在50 Hz到100 Hz之间。
    public static void removeNoise(String inputPath, String outputPath, int noise, int lowpass, int highpass) {
        //noise范围是-80~-20
        if (noise < -80 || noise > -20) {
            throw new RuntimeException("noise参数只能在-80~-20之间");
        }
        List<String> cmds = new ArrayList<>();
        cmds.add("-i");
        cmds.add(inputPath);
        cmds.add("-y");
        cmds.add("-af");
        cmds.add("lowpass="+lowpass+",highpass="+highpass+",afftdn=nf="+noise);
        cmds.add(outputPath);
        FfmpegInfoUtil.ExecuteCmd(cmds);
    }

    //音频高音低音调节
    //音频高音低音调节,支持调节高音和低音
    //high是高音,范围是-100~100,0是原音,100是最高音,-100是最低音  一般高音15~30就可以了
    //low是低音,范围是-100~100,0是原音,100是最高音,-100是最低音   一般低音-15~-30就可以了
    //调整高音和低音的时候,需要根据实际情况调整,不要调整太大,否则会导致声音失真, 这里给限制是-100~100
    public static void adjustTone(String inputPath, String outputPath, int high, int low) {
        //high和low范围是-100~100
        if (high < -100 || high > 100) {
            throw new RuntimeException("high参数只能在-100~100之间");
        }
        if (low < -100 || low > 100) {
            throw new RuntimeException("low参数只能在-100~100之间");
        }

        List<String> cmds = new ArrayList<>();
        cmds.add("-i");
        cmds.add(inputPath);
        cmds.add("-y");
        cmds.add("-af");
        cmds.add("treble=g="+high+",bass=g="+low);
        cmds.add(outputPath);
        FfmpegInfoUtil.ExecuteCmd(cmds);
    }

    //音频加混响 ,就是让声音有回声的效果 ,这个不要太狠,否则会导致声音不能听
    //一般的参数是time=0.8:decay=0.9:delay=1000:intensity=0.3
    //time是混响的时间,范围是0~1,0是没有混响,1是最大的混响,一般0.8
    //decay是混响的衰减,范围是0~1,0是没有混响,1是最大的混响,一般0.9
    //delay是混响的延迟,单位是毫秒,1000就是1秒,一般150就可以了
    //intensity是混响的强度,范围是0~1,0是没有混响,1是最大的混响,一般0.25就可以了
    public static void reverbAudio(String inputPath, String outputPath, double time, double decay, int delay, double intensity) {
        if (time < 0 || time > 1) {
            throw new RuntimeException("time参数只能在0~1之间");
        }
        if (decay < 0 || decay > 1) {
            throw new RuntimeException("decay参数只能在0~1之间");
        }
        if (delay < 0) {
            throw new RuntimeException("delay参数只能大于0 ,1000就是1秒");
        }
        if (intensity < 0 || intensity > 1) {
            throw new RuntimeException("intensity参数只能在0~1之间");
        }

        List<String> cmds = new ArrayList<>();
        cmds.add("-i");
        cmds.add(inputPath);
        cmds.add("-y");
        cmds.add("-af");
        cmds.add("aecho="+time+":"+decay+":"+delay+":"+intensity); //意思是混响的时间是0.8,混响的衰减是0.9,混响的延迟是1000,混响的强度是0.3
        cmds.add(outputPath);
        FfmpegInfoUtil.ExecuteCmd(cmds);
    }


    //声道转换
    //声道转换,支持声道转换,比如只左声道,只右声道,立体声道(左右声道都有)
    //ffmpeg -i stereo.wav -af "pan=1c|c0=c0" left_channel.wavFfmpegInfoUtil.ExecuteCmd(cmds);
    //ffmpeg -i stereo.wav -af "pan=1c|c0=c1" right_channel.wav
    //ffmpeg -i stereo.wav -af "pan=stereo|c0=c0|c1=c1" stereo_channel.wav
    //1:左声道,2:右声道,3:立体声道
    public static void channelConversion(String inputPath, String outputPath, int channel) {
        if (channel < 1 || channel > 3) {
            throw new RuntimeException("channel参数只能是1,2,3");
        }
        List<String> cmds = new ArrayList<>();
        cmds.add("-i");
        cmds.add(inputPath);
        cmds.add("-y");
        cmds.add("-af");
        if (channel == 1) {
            cmds.add("pan=1c|c0=c0");
        } else if (channel == 2) {
            cmds.add("pan=1c|c0=c1");
        } else {
            cmds.add("pan=stereo|c0=c0|c1=c1");
        }
        cmds.add(outputPath);
        FfmpegInfoUtil.ExecuteCmd(cmds);
    }





    //音频压缩 典型的速度介于128kbps和320kbps(kbit/s)之间 ,如果低于128kbps,那么就会导致音质下降,如果高于320kbps,那么就会导致文件过大
    // 反正只要用了就会体积变小, 但是原本音频已经压缩过了,再压缩就会不会有什么效果了
    // 测试从bilibili下载的音频3.9m压缩后就为2.3m 并且保证音质基本不变
    public static void compressAudio(String inputPath, String outputPath) {
        List<String> cmds = new ArrayList<>();
        cmds.add("-i");
        cmds.add(inputPath);
        cmds.add("-y");
        cmds.add("-acodec");
        cmds.add("libmp3lame");
        cmds.add("-ab");
        cmds.add("128k");
        cmds.add(outputPath);
        FfmpegInfoUtil.ExecuteCmd(cmds);
    }



    //提取视频中的音频
    //inputPath: 输入视频地址.mp4
    //outputPath: 输出音频地址.mp3
    public static void extractAudio(String inputPath, String outputPath) {
        List<String> cmds = new ArrayList<>();
        cmds.add("-i");
        cmds.add(inputPath);
        cmds.add("-vn");
        cmds.add("-y");
        cmds.add(outputPath);
        FfmpegInfoUtil.ExecuteCmd(cmds);
    }
    //给一个没有音频的视频添加空音频
    public static void addNullAudio(String inputPath, String outputPath) {
        List<String> cmds = new ArrayList<>();
        cmds.add("-i");
        cmds.add(inputPath);
        cmds.add("-f");
        cmds.add("lavfi");
        cmds.add("-i");
        cmds.add("anullsrc=channel_layout=stereo:sample_rate=44100");
        cmds.add("-c:v");
        cmds.add("copy");
        cmds.add("-c:a");
        cmds.add("aac");
        cmds.add("-shortest");
        cmds.add(outputPath);
        FfmpegInfoUtil.ExecuteCmd(cmds);
    }
    //去掉视频中的音频
    public static void removeAudio(String inputPath, String outputPath) {
        List<String> cmds = new ArrayList<>();
        cmds.add("-i");
        cmds.add(inputPath);
        cmds.add("-an");
        cmds.add("-y");
        cmds.add(outputPath);
        FfmpegInfoUtil.ExecuteCmd(cmds);
    }


    //将一段音频插入到另一个视频中的指定位置 ,插入进去的是多少秒,那么视频的时间就会变长多少秒,所以需要截取多出来的部分
    //inputPath: 输入视频地址.mp4
    //insertPath: 插入音频地址.mp3
    //outputPath: 输出视频地址.mp4
    //start: 插入音频的开始时间,单位是秒
    public static void insertAudioToVideo(String inputPath, String insertPath, String outputPath, int start) {
        int endTime = FfmpegInfoUtil.getTime(inputPath);
        //判断插入音频的时间是否大于视频剩余时间  假设视频总时长是10秒,插入位置5秒,那么插入音频的时间不能大于5秒,否则就会导致视频时间不够,之后的视频就定格了
        if (start > endTime-start) {
            throw new RuntimeException("插入音频的时间不能大于视频的时间");
        }
        String tempOut=FfmpegInfoUtil.getOutputCachePath(outputPath,"insertAudioToVideo.mp4");

        List<String> cmds = new ArrayList<>();
        cmds.add("-i");
        cmds.add(inputPath);
        cmds.add("-i");
        cmds.add(insertPath);
        cmds.add("-y");
        cmds.add("-filter_complex");
        cmds.add("[0:a]atrim=end=" + start + "[a0];[1:a]atrim=start=0:end=" + endTime + ",asetpts=PTS-STARTPTS[a1];[0:a]atrim=start=" + start + ",asetpts=PTS-STARTPTS[a2];[a0][a1][a2]concat=n=3:v=0:a=1[outa]");
        cmds.add("-map");
        cmds.add("0:v");
        cmds.add("-map");
        cmds.add("[outa]");
        cmds.add(tempOut);
        FfmpegInfoUtil.ExecuteCmd(cmds);

        // 去掉多余的部分
        FfmpegVideoUtil.cutVideo(tempOut, outputPath,0, endTime);

        // 删除临时文件
        new File(tempOut).delete();

    }


}
